package org.huangrui.redis.lock;

import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Author hr
 * @Create 2024-05-17 02:18
 */
//@Component 引入DistributedLockFactory工厂模式，从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {
    private final StringRedisTemplate stringRedisTemplate;
    private final String lockName;//KEYS[1]
    private final String uuidValue;//ARGV[1]
    private long   expireTime;//ARGV[2]
    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName){
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();//UUID:ThreadID
        this.expireTime = 30L;
    }

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuid+":"+Thread.currentThread().getId();//UUID:ThreadID
        this.expireTime = 30L;
    }

    @Override
    public void lock() {
        tryLock();
    }


    @Override
    public boolean tryLock() {
        try {
            tryLock(-1,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return false;
    }

    /**
     * 干活的，实现加锁功能，实现这一个干活的就OK，全盘通用
     * @param time the maximum time to wait for the lock
     * @param unit the time unit of the {@code time} argument
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        System.out.println("lock(): lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);
        if (time != -1){
            this.expireTime = unit.toSeconds(time);
        }
        String luaScript = """
                if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then
                  redis.call('hincrby',KEYS[1],ARGV[1],1)
                  redis.call('expire',KEYS[1],ARGV[2])
                  return 1
                else
                  return 0
                end""";
        while (Boolean.FALSE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class), List.of(lockName), uuidValue, String.valueOf(expireTime)))){
            //暂停60毫秒
            try { TimeUnit.MILLISECONDS.sleep(60);} catch (InterruptedException e) {e.printStackTrace();}
        }
        //新建一个后台扫描程序，来坚持key目前的ttl，是否到我们规定的1/2 1/3来实现续期
        renewExpire();
        return true;
    }

    private void renewExpire() {
        String script ="""
                if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then
                  return redis.call('expire',KEYS[1],ARGV[2])
                else
                  return 0
                end""";

        new Timer().schedule(new TimerTask(){
            @Override
            public void run(){
                if (Boolean.TRUE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
                        Arrays.asList(lockName), uuidValue, String.valueOf(expireTime)))) {
                    renewExpire();
                }
            }
        },(this.expireTime * 1000)/2);
    }

    /**
     * 干活的，实现解锁功能
     */
    @Override
    public void unlock() {
        System.out.println("unlock(): lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);
        String script = """
                if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then
                  return nil
                elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then
                  return redis.call('del',KEYS[1])
                else
                  return 0
                end
                """;
        // nil = false 1 = true 0 = false
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockName),uuidValue);
        if (flag == null){
            throw new RuntimeException("this lock doesn't exists，o(╥﹏╥)o");
        }
    }

    //===下面的redis分布式锁暂时用不到=======================================
    //===下面的redis分布式锁暂时用不到=======================================
    //===下面的redis分布式锁暂时用不到=======================================
    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
